#include "../include/ConnectionPool.hpp"
#include <stdexcept>
#include <thread>
#include <mysql/mysql.h>

ConnectionPool* ConnectionPool::getInstance() {
	// 静态局部变量的方式 只创建一个连接池实例对象
	static ConnectionPool instance;
	return &instance;
}

// 初始化连接池
// 连接池会预热部分的连接，避免首次请求时阻塞。
void ConnectionPool::init(const string& host, const string& user, const string& pwd, const string& dbName, unsigned int port, int maxConn, int timeout) {
	// 互斥锁确保线程安全
	lock_guard<mutex> lock(mutex_);
	// 给参数，设置连接
	this->host_ = host;
    this->user_ = user;
    this->pwd_ = pwd;
    this->dbName_ = dbName;
    this->port_ = port;
    this->maxConn_ = maxConn;
    this->connTimeout_ = timeout;

	// 提前创建部分数据库连接
	for (int i = 0; i < maxConn_ / 2; i++) {
		auto conn = createConnection();
		// 连接进队列
		connQueue_.push(conn);
	}
}

/**
 * @brief 创建新的数据库连接。
 *
 * 该函数负责创建一个新的 MySQL 连接，并配置连接超时、自动重连等选项。
 * 如果创建连接失败，将抛出异常。
 * 超时: enum mysql_option::MYSQL_OPT_CONNECT_TIMEOUT = 0
 * 自动重连: enum mysql_option::MYSQL_OPT_RECONNECT = 15
 * 
 * @return MYSQL* 返回新的数据库连接
 */
MYSQL* ConnectionPool::createConnection() {
	// 对象
	MYSQL* conn = mysql_init(nullptr);
	// 失败就抛异常
	if (!conn) 	throw runtime_error("MySQL 初始化失败");

	// 连接超时设置
	mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &connTimeout_);
	// 连接数据库
    if (!mysql_real_connect(conn, host_.c_str(), user_.c_str(), pwd_.c_str(), dbName_.c_str(), port_, nullptr, 0)) {
        throw std::runtime_error(mysql_error(conn));  // 如果连接失败，抛出异常
    }
	// 然后就重连
	// my_bool reconnect = 1
	bool reconnect = true;
	mysql_options(conn, MYSQL_OPT_RECONNECT, &reconnect);
	// 更新当前连接数
	curConn_++;
	return conn;
}

/**
 * @brief 验证数据库连接是否有效。
 *
 * 该函数通过发送 ping 请求来检查连接是否有效。如果连接正常，则返回 true。
 * 
 * @param conn 需要验证的数据库连接
 * @return bool 如果连接有效，返回 true；否则返回 false
 */
bool ConnectionPool::validateConnection(MYSQL* conn) {
	return mysql_ping(conn) == 0;
}

/**
 * @brief 获取一个数据库连接。
 *
 * 该函数从连接池中获取一个可用的数据库连接。如果连接池为空且当前连接数未达到最大连接数，则会创建新的连接并返回。如果超时仍未获取到连接，则返回 nullptr。使用智能指针来管理连接的生命周期，自动归还连接。
 * 
 * @param timeout 获取连接的超时时间，单位为毫秒，默认为3000ms
 * @return std::shared_ptr<MYSQL> 获取到的数据库连接
 */
shared_ptr<MYSQL> ConnectionPool::getConnection(int timeout) {
	// 唯一锁保护资源访问
	unique_lock<mutex> lock(mutex_);
	// 超时时间计算
	auto waitUntil = chrono::system_clock::now() + chrono::milliseconds(timeout);
	// 池子空了就 创建新连接
	while (connQueue_.empty()) {
		// 没到最大连接数就创建新的
		if (curConn_ < maxConn_) {
			auto conn = createConnection();
			return shared_ptr<MYSQL>(conn, [this](MYSQL* conn) {
				lock_guard<mutex> lock(mutex_);
				if (validateConnection(conn)) {
					connQueue_.push(conn);		// 有效 进池的任务队列中去
				} else {
					// 无效就关闭连接
					mysql_close(conn);
					curConn_--;
				}
				// 报告给 等待中的线程
				cond_.notify_one();
			});
		}

		// 等待线程 直到超时 or 获取到连接
		if (cond_.wait_until(lock,waitUntil) == cv_status::timeout) {
			return nullptr;		// 超时 没有获取到连接 就回nullptr
		}
	}

	// 正常情况
	auto conn = connQueue_.front();	// 队头取到连接
	// 弹出
	connQueue_.pop();

	return shared_ptr<MYSQL>(conn, [this](MYSQL* conn) {
		lock_guard<mutex> lock(mutex_);
		if (validateConnection(conn)) {
			connQueue_.push(conn);		// 有效 进池的任务队列中去
		} else {
			// 无效就关闭连接
			mysql_close(conn);
			curConn_--;
		}
		// 报告给 等待中的线程
		cond_.notify_one();
	});
}

/**
 * @brief 获取连接池的状态。
 * 该函数返回连接池的当前状态，包括连接池中总的连接数和空闲连接数。
 * @return PoolStats 连接池的当前状态
 */
ConnectionPool::PoolStats ConnectionPool::getStats() const {
    lock_guard<std::mutex> lock(mutex_);
	// 类型强转 struct 中成员是两个int
    return {curConn_, static_cast<int>(connQueue_.size())};  // 返回当前连接池的状态
}
